/* 
 * Copyright (c) 2012, Fromentin Xavier, Schnell Michaël, Dervin Cyrielle, Brabant Quentin
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *      * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *      * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *      * The names of its contributors may not be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Fromentin Xavier, Schnell Michaël, Dervin Cyrielle OR Brabant Quentin 
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package gword.generateur;

import gword.exception.WordGenerationException;

import java.io.File;
import java.lang.reflect.Method;
import java.util.Hashtable;

import kameleon.document.Array;
import kameleon.document.BulletListElement;
import kameleon.document.Cell;
import kameleon.document.Document;
import kameleon.document.ElementPropertiesDefaultNames;
import kameleon.document.HorizontalSeparator;
import kameleon.document.HyperTextLink;
import kameleon.document.LineBreak;
import kameleon.document.ListElement;
import kameleon.document.MailLink;
import kameleon.document.NumberedListElement;
import kameleon.document.Paragraph;
import kameleon.document.Paragraphs;
import kameleon.document.Row;
import kameleon.document.Text;
import kameleon.document.TextParagraph;
import kameleon.document.TextParagraphElement;
import kameleon.document.Title;
import kameleon.exception.InvalidPropertyException;
import kameleon.plugin.SupportedOptions;

/**
 * //TODO
 * 
 * @author		Schnell Michaël
 * @version		1.0
 */
public class DocumentBodyGenerator 
implements WordConstants, GenerationConstants, WordTags, ElementPropertiesDefaultNames {
		
	public static final Hashtable<String, String> FORMAT_WORD = 
			new Hashtable<String, String>() {{
				put(ElementPropertiesDefaultNames.FORMAT_BOLD,        WordTags.FORMAT_BOLD) ;
				put(ElementPropertiesDefaultNames.FORMAT_ITALIC,      WordTags.FORMAT_ITALIC) ;
				put(ElementPropertiesDefaultNames.FORMAT_STRUCK,      WordTags.FORMAT_STRUCK) ;
				put(ElementPropertiesDefaultNames.FORMAT_SUPERSCRIPT, WordTags.FORMAT_SUPERSCRIPT) ;
				put(ElementPropertiesDefaultNames.FORMAT_SUBSCRIPT,   WordTags.FORMAT_SUBSCRIPT) ;
			}} ;
	
	protected boolean debugMode ;

	//TODO Add javadoc
	protected File targetFile ;
	protected Document base ;
	protected SupportedOptions options ;
	
	/**
	 * Sole constructor.
	 */
	public DocumentBodyGenerator(boolean debugMode) {
		super() ;
		this.debugMode = debugMode ;
	}// DocumentBodyGenerator()
	
	/**
	 * Generates the XML code for the {@code <w:body>} tag of the document.
	 * 
	 * @return	XML code for the {@code <w:body>} tag of the document
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 */
	protected String generateBody() throws WordGenerationException {
		return this.generate(this.base.getParagraphs()) ;
	}// generateBody()
	
	/**
	 * Generates the XML code for the given list of paragraphs.
	 * 
	 * @param	paragrahs
	 * 			instance of {@code Paragraphs} containing the list of paragraphs
	 * 
	 * @return	XML code for the given list of paragraphs
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 */
	private String generate(Paragraphs paragrahs) throws WordGenerationException {
		final Class<?> thisClass = DocumentBodyGenerator.class ;
		StringBuilder body = new StringBuilder() ;
		
		for (Paragraph p : paragrahs) {
			try {
				Method method = thisClass.getDeclaredMethod(
						GENERATE_FUNCTION, p.getClass()) ;
				body.append(method.invoke(this, p)) ;
			} catch (Exception ex) {
//				if (this.debugMode) {
//					throw new WordGenerationException(this.targetFile) ;
//				}// if
			}// try
		}// for
		
		return body.toString() ;
	}// generate(Paragraphs)
	
	/**
	 * Generates the XML code for a {@code TextParagraph}.
	 * 
	 * <p>The generated code start with a {@code <w:p>} tag and
	 * contains the given text.
	 * 
	 * @param 	tp
	 * 			source for the generated text
	 * 
	 * @return	XML code for a {@code TextParagraph}
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code 
	 */
	protected String generate(TextParagraph tp) throws WordGenerationException {
		final Class<?> thisClass = DocumentBodyGenerator.class ;
		StringBuilder textP = new StringBuilder() ;

		textP.append(PARAGRAPH_START) ;
		for(TextParagraphElement pe : tp) {
			try {
				Method methode = thisClass.getDeclaredMethod(
						GENERATE_FUNCTION, pe.getClass()) ;
				textP.append(methode.invoke(this, pe)) ;
			} catch (Exception ex) {
				if (this.debugMode) {
					throw new WordGenerationException(this.targetFile, ex) ;
				}// if
			}// try
		}// for
		textP.append(PARAGRAPH_END);
		
		return textP.toString() ;
	}// generate(TextParagraph)
	
	/**
	 * Generates the XML code for a {@code BulletListElement}.
	 * 
	 * <p>The generated code start with a {@code <w:p>} tag and
	 * contains the given text.
	 * 
	 * @param 	element
	 * 			source for the generated text
	 * 
	 * @return	XML code for a {@code BulletListElement}
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 * 
	 * @throws 	InvalidPropertyException
	 * 			if an invalid key was requested
	 */
	protected String generate(BulletListElement element) 
			throws WordGenerationException, InvalidPropertyException {
		return this.generate(element, BULLET_LIST_ID) ;
	}// generate(BulletListElement)
	
	/**
	 * Generates the XML code for a {@code NumberedListElement}.
	 * 
	 * <p>The generated code start with a {@code <w:p>} tag and
	 * contains the given text.
	 * 
	 * @param 	element
	 * 			source for the generated text
	 * 
	 * @return	XML code for a {@code NumberedListElement}
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 * 
	 * @throws 	InvalidPropertyException
	 * 			if an invalid key was requested
	 */
	protected String generate(NumberedListElement element) 
			throws WordGenerationException, InvalidPropertyException {
		return this.generate(element, NUMBERED_LIST_ID) ;
	}// generate(NumberedListElement)

	/**
	 * Generates the XML code for a {@code ListElement}.
	 * 
	 * <p>The generated code start with a {@code <w:p>} tag and
	 * contains the given text.
	 * 
	 * @param 	listElem
	 * 			source for the generated text
	 * 
	 * @param	id
	 * 			list style identifier used for this list element
	 * 
	 * @return	XML code for a {@code ListElement}
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 * 
	 * @throws 	InvalidPropertyException
	 * 			if an invalid key was requested
	 */
	private String generate(ListElement listElem, Integer id) 
			throws WordGenerationException, InvalidPropertyException {
		final Class<?> thisClass = DocumentBodyGenerator.class ;
		StringBuilder element = new StringBuilder() ;

		element.append(String.format(LIST_ELEMENT_START,
				listElem.getProperty(LIST_LEVEL), id)) ;
		for(TextParagraphElement pe : listElem) {
			try {
				Method methode = thisClass.getDeclaredMethod(
						GENERATE_FUNCTION, pe.getClass()) ;
				element.append(methode.invoke(this, pe)) ;
			} catch (Exception ex) {
				if (this.debugMode) {
					throw new WordGenerationException(this.targetFile, ex) ;
				}// if
			}// try
		}// for
		element.append(LIST_ELEMENT_END);
		
		return element.toString() ;
	}// generate(ListElement)
	
	/**
	 * Generates the XML code for a {@code TextParagraph}.
	 * 
	 * <p>The generated code start with a {@code <w:p>} tag and
	 * contains the given text.
	 * 
	 * @param 	title
	 * 			source for the generated text
	 * 
	 * @return	XML code for a {@code TextParagraph}
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 *  
	 * @throws 	InvalidPropertyException
	 * 			if an invalid key was requested
	 */
	protected String generate(Title title) 
			throws WordGenerationException, InvalidPropertyException {
		StringBuilder titleP = new StringBuilder() ;
		String styleName = String.format(HEADING_NAME, 
				title.getProperty(TITLE_LEVEL)) ;
		String content = escape((String) title.getProperty(TEXT_BODY)) ;
		
		titleP.append(PARAGRAPH_START) ;
		titleP.append(String.format(PARAGRAPH_PROPERTIES, styleName)) ;
		titleP.append(RUN_START) ;
		titleP.append(TEXT_START) ;
		titleP.append(content) ;
		titleP.append(TEXT_END) ;
		titleP.append(RUN_END) ;
		titleP.append(PARAGRAPH_END) ;
		
		return titleP.toString() ;
	}// generate(Title)
	
	/**
	 * Generates the XML code for a {@code Array}.
	 * 
	 * @param 	array
	 * 			source for the generated array
	 * 
	 * @return	XML code for a {@code Array}
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 *  
	 * @throws 	InvalidPropertyException
	 * 			if an invalid key was requested
	 */
	protected String generate(Array array) 
			throws WordGenerationException, InvalidPropertyException {
		StringBuilder arrayP = new StringBuilder() ;		
		
		arrayP.append(ARRAY_START) ;
		arrayP.append(ARRAY_PROPERTIES) ;
		arrayP.append(ARRAY_PROPERTIES_GRID_WIDTH_START) ;
		int nCols = array.iterator().next().getCount() ;
		Integer colWidth = new Integer(
				DEFAULT_TABLE_WIDTH.intValue() / nCols) ;
		for(int col=0; col<nCols; ++col) {
			arrayP.append(String.format(ARRAY_PROPERTIES_GRID_WIDTH, 
					colWidth)) ;
		}// for
		arrayP.append(ARRAY_PROPERTIES_GRID_WIDTH_END) ;
		for(Row row : array) {
			arrayP.append(this.generate(row)) ;
		}// for
		arrayP.append(ARRAY_END) ;
		
		return arrayP.toString() ;
	}// generate(Array)
	
	/**
	 * Generates the XML code for a {@code Row}.
	 * 
	 * @param 	row
	 * 			source for the generated row
	 * 
	 * @return	XML code for a {@code Row}
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 *  
	 * @throws 	InvalidPropertyException
	 * 			if an invalid key was requested
	 */
	protected String generate(Row row) 
			throws WordGenerationException, InvalidPropertyException {
//		final Class<?> thisClass = DocumentBodyGenerator.class ;
		StringBuilder rowP = new StringBuilder() ;		
		
		rowP.append(ROW_START) ;
		for(Cell cell : row) {
//			try {
//				Method methode = thisClass.getDeclaredMethod(
//						GENERATE_FUNCTION, cell.getClass()) ;
//				rowP.append(methode.invoke(this, cell)) ;
//			} catch (Exception ex) {
//				if (this.debugMode) {
//					throw new WordGenerationException(this.targetFile) ;
//				}// if
//			}// try
			rowP.append(this.generate(cell)) ;
		}// for
		rowP.append(ROW_END) ;
		
		return rowP.toString() ;
	}// generate(Row)
	
	/**
	 * Generates the XML code for a {@code Cell}.
	 * 
	 * @param 	cell
	 * 			source for the generated cell
	 * 
	 * @return	XML code for a {@code Cell}
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 *  
	 * @throws 	InvalidPropertyException
	 * 			if an invalid key was requested
	 */
	protected String generate(Cell cell) 
			throws WordGenerationException, InvalidPropertyException {
		StringBuilder cellP = new StringBuilder() ;		
		boolean generateContent = true ;
		
		cellP.append(CELL_START) ;
		if (Boolean.TRUE.equals(cell.getProperty(ROW_SPAN))) {
			cellP.append(CELL_ROW_SPAN) ;
			generateContent = false ;
		}// if
		if (Boolean.TRUE.equals(cell.getProperty(COL_SPAN))) {
			cellP.append(CELL_COL_SPAN) ;
			generateContent = false ;
		}// if
		if (generateContent) {
			cellP.append(this.generate(cell.getParagraphs())) ;
		}// if		
		cellP.append(CELL_END) ;
		
		return cellP.toString() ;
	}// generate(Cell)
	
	/**
	 * Generates the XML code for a {@code HorizontalSeparator}.
	 * 
	 * @param 	separator
	 * 			source for the generated separator
	 * 
	 * @return	XML code for a {@code HorizontalSeparator}
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 *  
	 * @throws 	InvalidPropertyException
	 * 			if an invalid key was requested
	 */
	protected String generate(HorizontalSeparator separator) 
			throws WordGenerationException, InvalidPropertyException {
		return HORIZONTAL_SEPARATOR ;
	}// generate(HorizontalSeparator)
	
	/**
	 * Generates the XML code for a {@code Text}.
	 * 
	 * @param 	text
	 * 			source for the generated text
	 * 
	 * @return	XML code for a {@code Text}
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 *  
	 * @throws 	InvalidPropertyException
	 * 			if an invalid key was requested
	 */
	protected String generate(Text text) 
			throws WordGenerationException, InvalidPropertyException {
		StringBuilder textP = new StringBuilder() ;
		String content = escape((String) text.getProperty(TEXT_BODY)) ;
		
		textP.append(RUN_START) ;
		textP.append(RUN_PROPERTIES_START) ;
		for(String format : FORMAT_WORD.keySet()) {
			if (Boolean.TRUE.equals(text.getProperty(format))) {
				textP.append(FORMAT_WORD.get(format)) ;
			}// if
		}// for
		textP.append(RUN_PROPERTIES_END) ;
		textP.append(TEXT_START) ;
		textP.append(content) ;
		textP.append(TEXT_END) ;
		textP.append(RUN_END) ;
		
		return textP.toString() ;
	}// generate(Text)
	
	/**
	 * Generates the XML code for a {@code LineBreak}.
	 * 
	 * @param 	lineBreak
	 * 			source for the generated line break
	 * 
	 * @return	XML code for a {@code LineBreak}
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 *  
	 * @throws 	InvalidPropertyException
	 * 			if an invalid key was requested
	 */
	protected String generate(LineBreak lineBreak) 
			throws WordGenerationException, InvalidPropertyException {
		return LINE_BREAK ;
	}// generate(LineBreak)
	
	/**
	 * Generates the XML code for a {@code MailLink}.
	 * 
	 * @param 	link
	 * 			source for the generated link
	 * 
	 * @return	XML code for a {@code MailLink}
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 *  
	 * @throws 	InvalidPropertyException
	 * 			if an invalid key was requested
	 */
	protected String generate(MailLink link) 
			throws WordGenerationException, InvalidPropertyException {
		final Class<?> thisClass = DocumentBodyGenerator.class ;
		StringBuilder mailP = new StringBuilder() ;
		String destination = (String) link.getProperty(MAIL_DESTINATION) ;
		
		mailP.append(String.format(MAIL_LINK_START, destination)) ;
		mailP.append(RUN_START) ;
		mailP.append(LINK_RUN) ;
		for(TextParagraphElement pe : link){
			try {
				Method methode = thisClass.getDeclaredMethod(
						GENERATE_FUNCTION, pe.getClass()) ;
				mailP.append(methode.invoke(this, pe)) ;
			} catch (Exception ex) {
				if (this.debugMode) {
					throw new WordGenerationException(this.targetFile, ex) ;
				}// if
			}// try
		}// for
		mailP.append(RUN_END) ;
		mailP.append(MAIL_LINK_END) ;
		
		return mailP.toString() ;
	}// generate(MailLink)
	
	/**
	 * Generates the XML code for a {@code HyperTextLink}.
	 * 
	 * @param 	link
	 * 			source for the generated link
	 * 
	 * @return	XML code for a {@code HyperTextLink}
	 * 
	 * @throws 	WordGenerationException
	 * 			if an error occurred while generating the XML code
	 *  
	 * @throws 	InvalidPropertyException
	 * 			if an invalid key was requested
	 */
	protected String generate(HyperTextLink link) 
			throws WordGenerationException, InvalidPropertyException {
		final Class<?> thisClass = DocumentBodyGenerator.class ;
		StringBuilder mailP = new StringBuilder() ;
		String destination = (String) link.getProperty(MAIL_DESTINATION) ;
		
		mailP.append(String.format(HYPERTEXT_LINK_START, destination)) ;
		mailP.append(RUN_START) ;
		mailP.append(LINK_RUN) ;
		for(TextParagraphElement pe : link){
			try {
				Method methode = thisClass.getDeclaredMethod(
						GENERATE_FUNCTION, pe.getClass()) ;
				mailP.append(methode.invoke(this, pe)) ;
			} catch (Exception ex) {
				if (this.debugMode) {
					throw new WordGenerationException(this.targetFile, ex) ;
				}// if
			}// try
		}// for
		mailP.append(RUN_END) ;
		mailP.append(HYPERTEXT_LINK_END) ;
		
		return mailP.toString() ;
	}// generate(HyperTextLink)
	
	/**
	 * Escapes the special characters of the given {@code String} so
	 * that they can be inserted in a XML file.
	 * 
	 * <p>The following characters are currently escaped:
	 * <ul>
	 * <li>{@code & --> &amp;}
	 * <li>{@code < --> &lt;}
	 * <li>{@code > --> &gt;}
	 * </ul>
	 * 
	 * @param 	str
	 * 			text which should be escaped
	 * 
	 * @return	escaped version of {@code str}
	 */
	public static String escape(String str) {
		return str.replaceAll("&", "&amp;")  //$NON-NLS-1$//$NON-NLS-2$
				.replaceAll("<", "&lt;") //$NON-NLS-1$ //$NON-NLS-2$
				.replaceAll(">", "&gt;") ; //$NON-NLS-1$ //$NON-NLS-2$
	}// escape(String)

}// class DocumentBodyGenerator